/*
 * YahooPipe Adapter. Converts a pipe's JSON representation into a data mashup canonical model
 * Copyright (C) 2011 Carlos Rodriguez
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package eu.ict_omelette.mashup.adapters.pipes;

import eu.ict_omelette.mashup.canonicalmodel.CanonicalMashup;
import eu.ict_omelette.mashup.canonicalmodel.Component;
import eu.ict_omelette.mashup.canonicalmodel.Dataflow;
import eu.ict_omelette.mashup.canonicalmodel.GlobalParameter;
import eu.ict_omelette.mashup.canonicalmodel.InputParameter;
import eu.ict_omelette.mashup.transformer.api.AdapterPlugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author carlos
 */
public class PipesAdapter implements AdapterPlugin{

    private CanonicalMashup canMashup = new CanonicalMashup();

    public void loadSourceMashupModel(URI uri) throws MalformedURLException{
        File file = new File(uri);

        try {

            BufferedReader reader = new BufferedReader(new FileReader(file));

            canMashup.setId(uri.getPath());

            Pipe pipe = new Pipe(reader);            

            /***************************************************
             * Here we load the modules (components) of the Pipe
             ***************************************************/
            LinkedList modules = pipe.getModules();
            Iterator moduleIt = modules.iterator();
            while(moduleIt.hasNext()){
                Map module = (Map)moduleIt.next();
                String type = (String)module.get("type");
                Component comp = new Component();
                
                if(type.contains("pipe:")){                    

                    comp.setId((String)module.get("id"));
                    comp.setIsEmbedding(false);
                    comp.setType((String)module.get("type"));
                    comp.setName((String)module.get("type"));

                    //System.out.println(comp.getId());

                }else{                    

                    comp.setId((String)module.get("id"));
                    comp.setIsEmbedding(false);
                    comp.setType((String)module.get("type"));
                    comp.setName((String)module.get("type"));

                    //System.out.println(comp.getName());


                    /*****************************
                    * Here we load the parameters
                    ******************************/

                    String compName = comp.getName();

                    if(compName.equals("loop")){
                        canMashup.addComponent(comp);
                        continue; //there no parameters here
                    }

                    //The following modules are treated separatedly, because
                    //of their complex, heterogenous structures
                    if(compName.equals("urlbuilder")){
                        convertURLBuilderInputParams((LinkedHashMap)module.get("conf"), comp);
                        canMashup.addComponent(comp);
                        continue;
                    }else if(compName.equals("filter")){
                        convertFilterInputParams((LinkedHashMap)module.get("conf"), comp);
                        canMashup.addComponent(comp);
                        continue;
                    }else if(compName.equals("foreachannotate")){
                        convertForEachAnnotateParams((LinkedHashMap)module.get("conf"), comp);
                        canMashup.addComponent(comp);
                        continue;
                    }else if(compName.equals("foreach")){
                        convertForEachParams((LinkedHashMap)module.get("conf"), comp);
                        canMashup.addComponent(comp);
                        continue;
                    }else if(compName.equals("itembuilder")){
                        convertItemBuilderParams((LinkedHashMap)module.get("conf"), comp);
                        canMashup.addComponent(comp);
                        continue;                        
                    }else if(compName.equals("sort")){
                        convertSortParams((LinkedHashMap)module.get("conf"), comp);
                        canMashup.addComponent(comp);
                        continue;
                    }

                    //System.out.println(comp.getName());

                    //In the following we get the parameters for those modules
                    //that are homogenous in the representation of parameters.
                    LinkedHashMap conf = (LinkedHashMap)module.get("conf");
                    if(conf != null){
                        Iterator itConf = conf.keySet().iterator();
                        while(itConf.hasNext()){
                            String keyConf = (String)itConf.next();

                            //Debugging code
                            if(compName.equals("sort")){
                                compName = compName + "";
                             }
                            
                                                               
                            if(conf.get(keyConf) instanceof LinkedHashMap){
                                LinkedHashMap lhm = (LinkedHashMap)conf.get(keyConf);

                                String ipType = (String)lhm.get("type");
                                String ipValue = (String)lhm.get("value");
                                String ipTerminal = (String)lhm.get("terminal");

                                InputParameter ip = new InputParameter();
                                ip.setName(keyConf);
                                ip.setType(ipType);


                                //We treat input parameters with "terminal" in a especial way
                                //We set the "value" of the InputParameter with the word "terminal" +
                                //the actual value of the "terminal" attribute coming from the JSON file
                                //WE NEED TO TAKE THIS INTO ACCOUNT WHEN WE DO CANONICAL-TO-PIPE
                                if(ipTerminal != null){
                                    ip.setValue("terminal:" + ipTerminal);
                                }else{
                                    ip.setValue(ipValue);
                                }

                                comp.addInputParameter(ip);
                                
                            }else{

                                LinkedList ll = (LinkedList)conf.get(keyConf);
                                
                                int ruleCount = -1;
                                Iterator<LinkedHashMap> itIp = ll.iterator();
                                while(itIp.hasNext()){
                                    LinkedHashMap ipLhm = itIp.next();
                                    ruleCount++;

                                    String parName = keyConf + "_" + ruleCount;                                   


                                    String value = (String)ipLhm.get("value");
                                    String dataType = (String)ipLhm.get("type");
                                    String terminal = (String)ipLhm.get("terminal");

                                    InputParameter ip = new InputParameter();
                                    ip.setName(parName);
                                    ip.setType(dataType);
                                    
                                    if(terminal != null){
                                        ip.setValue("terminal:" + terminal);
                                    }else{
                                        ip.setValue(value);
                                    }


                                    comp.addInputParameter(ip);

                                    /*
                                    
                                    Iterator<String> it2 = ipLhm.keySet().iterator();
                                    while(it2.hasNext()){
                                        String attrName = it2.next();
                                        
                                        if(!(ipLhm.get(attrName) instanceof LinkedHashMap)){
                                            String value = (String)ipLhm.get("value");
                                            String dataType = (String)ipLhm.get("type");

                                            InputParameter ip = new InputParameter();
                                            ip.setName(parName);
                                            ip.setType(dataType);
                                            ip.setValue(value);

                                            comp.addInputParameter(ip);

                                            continue;                                            

                                        }else{
                                            
                                            //LinkedHashMap lhm3 = (LinkedHashMap)ipLhm.get(attrName);
                                            //Iterator<String> it3 = ipLhm.keySet().iterator();

                                            LinkedHashMap valType = (LinkedHashMap)ipLhm.get(attrName);

                                            InputParameter ip = new InputParameter();
                                            ip.setName(parName + "." + attrName);
                                            ip.setType((String)valType.get("type"));
                                            ip.setValue((String)valType.get("value"));

                                            comp.addInputParameter(ip);

                                            continue;

                                        }

                                    }
                                     */
                                     
                                    
                                }
                            }
                        }
                    }
                    
                
                    
                }

                canMashup.addComponent(comp);
                
            }


            /******************************************************
             * Here we load the wires (dataflow) between components
             ******************************************************/
            LinkedList wires = pipe.getWires();

            if(wires != null){
                Iterator wiresIt = wires.iterator();
                while(wiresIt.hasNext()){
                    Map wire = (Map)wiresIt.next();

                    //When a pipe does not have wires, for some reason, we still get
                    //until this part the code. If we do not check the that wire==null,
                    //we get a runtime exception
                    if(wire == null)
                        continue;

                    String from = wire.get("src").toString();
                    String to = wire.get("tgt").toString();

                    from = from.replaceAll("\\{", "");
                    from = from.replaceAll("\\}", "");
                    from = from.replaceAll(" ", "");

                    to = to.replaceAll("\\{", "");
                    to = to.replaceAll("\\}", "");
                    to = to.replaceAll(" ", "");

                    String fromPair[]   = from.split(",");
                    String fromParam[]  = fromPair[0].split("=");
                    String fromModule[] = fromPair[1].split("=");

                    String toPair[]   = to.split(",");
                    String toParam[]  = toPair[0].split("=");
                    String toModule[] = toPair[1].split("=");

                    String fromModStr = fromModule[1];
                    String fromParamStr = fromParam[1];

                    String toModStr = toModule[1];
                    String toParamStr = toParam[1];

                    Component fromComp = canMashup.getComponent(fromModStr);
                    Component toComp = canMashup.getComponent(toModStr);

                    if((fromComp != null) && (toComp != null)){
                        //System.out.println(uri.toString() + ") From: " + fromModStr + "  to: " + toModStr);

                        Dataflow df = new Dataflow();
                        df.setFromPort(fromComp, fromParamStr);
                        df.setToPort(toComp, toParamStr);

                        canMashup.addDataflow(df);
                    }

                }
            }
            
        } catch (FileNotFoundException ex) {

            Logger.getLogger(PipesAdapter.class.getName()).log(Level.SEVERE, null, ex);

        }

        

        

    }

    public CanonicalMashup getCanonicalMashup() {
        return this.canMashup;
    }

    public List<Component> getComponents() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<Dataflow> getDataflows() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public List<GlobalParameter> getGlobalParameters() {
        throw new UnsupportedOperationException("Not supported yet.");
    }



    private void convertURLBuilderInputParams(LinkedHashMap conf, Component comp){
        LinkedHashMap lhm = (LinkedHashMap)conf.get("BASE");
        InputParameter ip = new InputParameter();
        ip.setName("BASE");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value"));

        comp.addInputParameter(ip);
        
        lhm = (LinkedHashMap)conf.get("PORT");
        if(lhm != null){
            ip = new InputParameter();
            ip.setName("PORT");
            ip.setType("" + lhm.get("type"));
            ip.setValue("" + lhm.get("value"));

            comp.addInputParameter(ip);
        }
        
        if(conf.get("PATH") instanceof LinkedList){
            LinkedList ll = (LinkedList)conf.get("PATH");
            int count  = -1;
            Iterator<LinkedHashMap> it = ll.iterator();
            while(it.hasNext()){
                LinkedHashMap lhmPath = it.next();

                count++;
                ip = new InputParameter();
                ip.setName("PATH_" + count);
                ip.setType("" + lhmPath.get("type"));

                if(lhmPath.get("value") != null)
                    ip.setValue("" + lhmPath.get("value"));
                else
                    ip.setValue("$" + lhmPath.get("terminal"));

                comp.addInputParameter(ip);
            }
        }else if(conf.get("PATH") instanceof LinkedHashMap){
            lhm = (LinkedHashMap)conf.get("PATH");
            ip = new InputParameter();
            ip.setName("PATH");
            ip.setType("" + lhm.get("type"));

            if(lhm.get("value") != null)
                ip.setValue("" + lhm.get("value"));
            else
                ip.setValue("$" + lhm.get("terminal"));

            comp.addInputParameter(ip);
        }


        if(conf.get("PARAM") instanceof LinkedList){
            LinkedList ll = (LinkedList)conf.get("PARAM");
            int count  = -1;
            Iterator<LinkedHashMap> it = ll.iterator();
            while(it.hasNext()){
                LinkedHashMap lhmParam = it.next();

                count++;
                LinkedHashMap lhmKey = (LinkedHashMap)lhmParam.get("key");
                ip = new InputParameter();
                ip.setName("PARAM_" + count + ".key");
                ip.setType("" + lhmKey.get("type"));
                if(lhmKey.get("value") != null)
                    ip.setValue("" + lhmKey.get("value"));
                else
                    ip.setValue("$" + lhmKey.get("terminal"));

                comp.addInputParameter(ip);

                LinkedHashMap lhmValue = (LinkedHashMap)lhmParam.get("value");
                ip = new InputParameter();
                ip.setName("PARAM_" + count + ".value");
                ip.setType("" + lhmValue.get("type"));

                if(lhmValue.get("value") != null)
                    ip.setValue("" + lhmValue.get("value"));
                else
                    ip.setValue("$" + lhmValue.get("terminal"));

                comp.addInputParameter(ip);
            }
        }else if(conf.get("PARAM") instanceof LinkedHashMap){
            lhm = (LinkedHashMap)conf.get("PARAM");

            LinkedHashMap lhmKey = (LinkedHashMap)lhm.get("key");
            ip = new InputParameter();
            ip.setName("PARAM" + ".key");
            ip.setType("" + lhmKey.get("type"));
            if(lhmKey.get("value") != null)
                ip.setValue("" + lhmKey.get("value"));
            else
                ip.setValue("$" + lhmKey.get("terminal"));

            comp.addInputParameter(ip);

            LinkedHashMap lhmValue = (LinkedHashMap)lhm.get("value");
            ip = new InputParameter();
            ip.setName("PARAM" + ".value");
            ip.setType("" + lhmValue.get("type"));

            if(lhmValue.get("value") != null)
                ip.setValue("" + lhmValue.get("value"));
            else
                ip.setValue("$" + lhmValue.get("terminal"));

            comp.addInputParameter(ip);
        }        

    }

    private void convertFilterInputParams(LinkedHashMap conf, Component comp){
        LinkedHashMap lhm = (LinkedHashMap)conf.get("MODE");
        InputParameter ip = new InputParameter();
        ip.setName("MODE");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value"));

        comp.addInputParameter(ip);

        lhm = (LinkedHashMap)conf.get("COMBINE");
        ip = new InputParameter();
        ip.setName("COMBINE");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value"));

        comp.addInputParameter(ip);

        if(conf.get("RULE") instanceof LinkedList){
            LinkedList ll = (LinkedList)conf.get("RULE");
            int count  = -1;
            Iterator<LinkedHashMap> it = ll.iterator();
            while(it.hasNext()){
                LinkedHashMap lhmRule = it.next();
                count++;
                
                LinkedHashMap field = (LinkedHashMap)lhmRule.get("field");

                if(field != null){
                    ip = new InputParameter();
                    ip.setName("RULE_"+count+".field");
                    ip.setType("" + field.get("type"));
                    ip.setValue("" + field.get("value"));
                    comp.addInputParameter(ip);
                }


                LinkedHashMap op = (LinkedHashMap)lhmRule.get("op");
                if(op != null){
                    ip = new InputParameter();
                    ip.setName("RULE_"+count+".op");
                    ip.setType("" + op.get("type"));
                    ip.setValue("" + op.get("value"));
                    comp.addInputParameter(ip);
                }

                LinkedHashMap value = (LinkedHashMap)lhmRule.get("value");
                if(value != null){
                    ip = new InputParameter();
                    ip.setName("RULE_"+count+".value");
                    ip.setType("" + value.get("type"));
                    ip.setValue("" + value.get("value"));
                    comp.addInputParameter(ip);
                }

            }
            
        }else if(conf.get("RULE") instanceof LinkedHashMap){
            lhm = (LinkedHashMap)conf.get("RULE");

            LinkedHashMap field = (LinkedHashMap)lhm.get("field");

            ip = new InputParameter();
            ip.setName("RULE.field");
            ip.setType("" + field.get("type"));
            ip.setValue("" + field.get("value"));
            comp.addInputParameter(ip);

            LinkedHashMap op = (LinkedHashMap)lhm.get("op");
            ip = new InputParameter();
            ip.setName("RULE.op");
            ip.setType("" + op.get("type"));
            ip.setValue("" + op.get("value"));
            comp.addInputParameter(ip);

            LinkedHashMap value = (LinkedHashMap)lhm.get("value");
            ip = new InputParameter();
            ip.setName("RULE.value");
            ip.setType("" + value.get("type"));
            ip.setValue("" + value.get("value"));
            comp.addInputParameter(ip);
        }
        
    }

    private void convertForEachAnnotateParams(LinkedHashMap conf, Component comp){
        comp.setIsEmbedding(true);

        LinkedHashMap lhm = (LinkedHashMap)conf.get("key");
        InputParameter ip = new InputParameter();
        ip.setName("key");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value"));

        lhm = (LinkedHashMap)conf.get("MODE");
        ip = new InputParameter();
        ip.setName("MODE");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value"));

        lhm = (LinkedHashMap)conf.get("embed");
        ip = new InputParameter();
        ip.setName("embed.type");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value").toString());

        comp.addInputParameter(ip);


        //Here, I still need to check whether the *embeded component* just refer
        //to a component that is defined outside the *embedding component* or if
        //it is fully defined in the "value" parameter.
        Component embedComp = new Component();
        LinkedHashMap embed = (LinkedHashMap)lhm.get("value");        
        comp.setId(embed.get("id").toString());
        comp.setName(embed.get("type").toString());
        comp.setType(embed.get("type").toString());

        comp.addEmbeddedComponent(embedComp);
    }


    private void convertForEachParams(LinkedHashMap conf, Component comp){
        //System.out.println(conf.toString());

        comp.setIsEmbedding(true);

        LinkedHashMap lhm = (LinkedHashMap)conf.get("MODE");
        InputParameter ip = new InputParameter();
        ip.setName("MODE");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value"));


        lhm = (LinkedHashMap)conf.get("embed");
        ip = new InputParameter();
        ip.setName("embed");
        ip.setType("" + lhm.get("type"));
        ip.setValue("" + lhm.get("value").toString());

        comp.addInputParameter(ip);


        //Here, I still need to check whether the *embeded component* just refer
        //to a component that is defined outside the *embedding component* or if
        //it is fully defined in the "value" parameter.
        Component embedComp = new Component();
        LinkedHashMap embed = (LinkedHashMap)lhm.get("value");
        comp.setId(embed.get("id").toString());
        comp.setName(embed.get("type").toString());
        comp.setType(embed.get("type").toString());

        comp.addEmbeddedComponent(embedComp);       
    }


    private void convertItemBuilderParams(LinkedHashMap conf, Component comp){

        if(conf.get("attrs") instanceof LinkedList){
            LinkedList attrs = (LinkedList)conf.get("attrs");
            Iterator<LinkedHashMap> it = attrs.iterator();
            int count = -1;
            while(it.hasNext()){
                LinkedHashMap lhm = it.next();

                count++;
                LinkedHashMap key = (LinkedHashMap)lhm.get("key");
                InputParameter ip = new InputParameter();
                ip.setName("attrs_" + count + ".key");
                ip.setType("" + key.get("type"));

                if(key.get("value") != null)
                    ip.setValue("" + key.get("value"));
                else
                    ip.setName("$" + key.get("terminal"));
                comp.addInputParameter(ip);

                //System.out.println(ip.getName());

                LinkedHashMap value = (LinkedHashMap)lhm.get("value");
                ip = new InputParameter();
                ip.setName("attrs_" + count + ".value");
                ip.setType("" + value.get("type"));

                if(value.get("value") != null)
                    ip.setValue("" + value.get("value"));
                else
                    ip.setValue("$" + value.get("terminal"));
                comp.addInputParameter(ip);

            }
        }
        
    }

    private void convertSortParams(LinkedHashMap conf, Component comp){
        if(conf.get("KEY") instanceof LinkedHashMap){
            LinkedHashMap key = (LinkedHashMap)conf.get("KEY");

            LinkedHashMap field = (LinkedHashMap)key.get("field");
            String fieldType = (String)field.get("type");
            String fieldValue = (String)field.get("value");

            LinkedHashMap dir = (LinkedHashMap)key.get("dir");
            String dirType = (String)dir.get("type");
            String dirValue = (String)dir.get("value");

            InputParameter ipField = new InputParameter();
            ipField.setName("KEY.field");
            ipField.setType(fieldType);
            ipField.setValue(fieldValue);
            comp.addInputParameter(ipField);

            InputParameter ipDir = new InputParameter();
            ipDir.setName("KEY.dir");
            ipDir.setType(dirType);
            ipDir.setValue(dirValue);
            comp.addInputParameter(ipDir);
        }else if(conf.get("KEY") instanceof LinkedList){
            LinkedList ll = (LinkedList)conf.get("KEY");

            Iterator<LinkedHashMap> itll = ll.iterator();
            int count = -1;
            while(itll.hasNext()){
                LinkedHashMap key = itll.next();

                count++;

                LinkedHashMap field = (LinkedHashMap)key.get("field");
                String fieldType = (String)field.get("type");
                String fieldValue = (String)field.get("value");

                LinkedHashMap dir = (LinkedHashMap)key.get("dir");
                String dirType = (String)dir.get("type");
                String dirValue = (String)dir.get("value");

                InputParameter ipField = new InputParameter();
                ipField.setName("KEY_" + count + ".field");
                ipField.setType(fieldType);
                ipField.setValue(fieldValue);
                comp.addInputParameter(ipField);

                InputParameter ipDir = new InputParameter();
                ipDir.setName("KEY_" + count + ".dir");
                ipDir.setType(dirType);
                ipDir.setValue(dirValue);
                comp.addInputParameter(ipDir);
            }
        }
    }
}
